#ifndef __Frustum_hpp__
#define __Frustum_hpp__

#include "Matrix4.hpp"
#include "Plane.hpp"

namespace ge {

//----------------------------------------------------------------------------//
// Frustum
//----------------------------------------------------------------------------//

class Frustum
{
 public:

     enum PlaneId
     {
          Right,
          Left,
          Top,
          Bottom,
          Near,
          Far,
     };

     Frustum& FromMatrix( const Matrix4& _m )
     {
          m_planes[Right].Set( _m(3,0) - _m(0,0), _m(3,1) - _m(0,1), _m(3,2) - _m(0,2), _m(3,3) - _m(0,3) ).Normalize();
          m_planes[Left].Set( _m(3,0) + _m(0,0), _m(3,1) + _m(0,1), _m(3,2) + _m(0,2), _m(3,3) + _m(0,3) ).Normalize();
          m_planes[Top].Set( _m(3,0) - _m(1,0), _m(3,1) - _m(1,1), _m(3,2) - _m(1,2), _m(3,3) - _m(1,3) ).Normalize();
          m_planes[Bottom].Set( _m(3,0) + _m(1,0), _m(3,1) + _m(1,1), _m(3,2) + _m(1,2), _m(3,3) + _m(1,3) ).Normalize();
          m_planes[Near].Set( _m(3,0) + _m(2,0), _m(3,1) + _m(2,1), _m(3,2) + _m(2,2), _m(3,3) + _m(2,3) ).Normalize();
          m_planes[Far].Set( _m(3,0) - _m(2,0), _m(3,1) - _m(2,1), _m(3,2) - _m(2,2), _m(3,3) - _m(2,3) ).Normalize();
          return *this;
     }  
     
     bool IntersectsWithSphere( const Vector3& _sphere_center, float _sphere_radius ) const
     {
          return IntersectsWithSphere( m_planes, _sphere_center, _sphere_radius );
     }

     bool Contains( const Vector3& _point ) const
     {
          return ContainsPoint( m_planes, _point );
     }

     bool Intersects( const AABBox& _aabb, bool* _fully = 0 ) const
     {
          if( IntersectsWithSphere( _aabb.Center(), _aabb.Radius() ) )
          {
               if( _fully )
               {
                    Vector3 _corners[ 8 ];
                    _aabb.GetAllCorners( _corners );
                    *_fully = true;
                    for( uint i = 0; i < 8; ++ i )
                    {
                         if( !Contains( _corners[i] ) )
                         {
                              *_fully = false;
                              break;
                         }
                    }
               }

               return true;
          }

          return false;
     }

     static bool IntersectsWithSphere( const Plane* _frustum_planes, const Vector3& _sphere_center, float _sphere_radius )
     {
          return ( _frustum_planes[0].ClassifySphereRelation( _sphere_center, _sphere_radius ) != Plane::Negative ) &&
                 ( _frustum_planes[1].ClassifySphereRelation( _sphere_center, _sphere_radius ) != Plane::Negative ) &&
                 ( _frustum_planes[2].ClassifySphereRelation( _sphere_center, _sphere_radius ) != Plane::Negative ) &&
                 ( _frustum_planes[3].ClassifySphereRelation( _sphere_center, _sphere_radius ) != Plane::Negative ) &&
                 ( _frustum_planes[4].ClassifySphereRelation( _sphere_center, _sphere_radius ) != Plane::Negative ) &&
                 ( _frustum_planes[5].ClassifySphereRelation( _sphere_center, _sphere_radius ) != Plane::Negative );
     }
     
     static bool ContainsPoint( const Plane* _frustum_planes, const Vector3& _point )
     {
          return ( _frustum_planes[0].ClassifyPointRelation( _point ) != Plane::Negative ) &&
                 ( _frustum_planes[1].ClassifyPointRelation( _point ) != Plane::Negative ) &&
                 ( _frustum_planes[2].ClassifyPointRelation( _point ) != Plane::Negative ) &&
                 ( _frustum_planes[3].ClassifyPointRelation( _point ) != Plane::Negative ) &&
                 ( _frustum_planes[4].ClassifyPointRelation( _point ) != Plane::Negative ) &&
                 ( _frustum_planes[5].ClassifyPointRelation( _point ) != Plane::Negative );
     }

     //static bool IntersectsWithAABBox( const Plane* _frustum_planes, const Vector3& _min

 protected:

     Plane m_planes[6];
};

} // namespace ge

#endif//__Frustum_hpp__
